<?php
/**
 * dar域对象
 * 
 * 一个简单的域对象示例：
 * <pre>
 * class TLog extends IDarDomain  {
 * 	public static $MODEL = array(
 * 		"tblName" => "tbl_log",
 * 		"fields" => array("id", "title", "content", 
 * 			"comments" => array(
 * 				"hasMany"=>array(
 * 					"mappedBy"=>"id", 
 * 					"model"=>"TComment", 
 * 					"field"=>"logId"
 * 				)
 * 			)
 * 		)
 * 	);
 * }
 * </pre>
 * 
 *
 * @package util.dar
 * @version $Id: IDarDomain.php 323 2008-05-14 05:49:20Z iwind $
 * @since 0.1
 */
 abstract class IDarDomain extends IObject implements ArrayAccess,IIterable {
	private $modifiedProperties = array();
	private $isPersist = false;
	private $originProperties = array();
	/**
	 * 管理器列表
	 *
	 * @var array
	 * @since 0.2
	 */
	private static $managers = array();
	
	/**
	 * 构造器
	 *
	 * @since 0.1
	 */
	public function __construct() {

	}
	
	/**
	 * 取得模型
	 *
	 * @return IDarModel
	 * @since 0.1
	 */
	public function getDarModel() {
		return IDarModelFactory::getInstance()->getModel(get_class($this));
	}
	
	/**
	 * 取得驱动
	 *
	 * @return IDarProvider
	 * @since 0.1
	 */
	public function getDarProvider() {
		return $this->getDarModel()->getProvider();
	}
	
	/**
	 * 取得表
	 *
	 * @return IDarTable
	 * @since 0.1
	 */
	public function getDarTable() {
		return $this->getDarModel()->getTable();
	}
	
	/**
	 * 取得ID字段
	 *
	 * @return IDarField
	 * @since 0.1
	 */
	public function getDarId() {
		return $this->getDarTable()->getId();
	}
	
	/**
	 * 模型管理器
	 * 
	 * 从0.2.1开始起，如果域对象没有设置$MODEL属性，则自动从数据库动态取出表结构，当然，会有一些性能上的损失
	 *
	 * @param string $class 模型类名
	 * @return IDarManager
	 * @since 0.1
	 */
	public static function manager($class) {
		if (array_key_exists($class, self::$managers)) {
			return self::$managers[$class];
		}
		$model = IDarModelFactory::getInstance()->getModel($class);
		$vars = get_class_vars($class);
		if (!isset($vars["MODEL"])) {
			$vars["MODEL"] = array();
//			throw new IDarException("can't find model defination in domain class. please define public \$MODEL in domain class.");
		}
		$manager = new IDarManager($model);
		self::$managers[$class] = $manager;
		return $manager;
	}
	
	/**
	 * 保存
	 * 
	 * 如果在数据库中已经存在，则更新
	 *
	 * @param array $properties
	 * @return IDarDomain
	 * @since 0.1
	 */
	public function save(array $properties = null) {
		$this->setDarFieldValues($properties);
		if ($this->isPersist()) {
			return $this->getDarProvider()->update($this);
		}
		else {
			return $this->getDarProvider()->save($this);
		}
	}
	
	/**
	 * 判断是否已经被持久化
	 *
	 * @return boolean
	 * @since 0.1
	 */
	public function isPersist() {
		return $this->isPersist;
	}
	
	/**
	 * 设置持久化状态
	 *
	 * @param boolean $isPersist 是否已经被持久化
	 * @since 0.1
	 */
	public function setDarPersist($isPersist) {
		$this->isPersist = $isPersist;
	}
	
	/**
	 * 删除
	 *
	 * 返回影响的行数
	 * 
	 * @return integer
	 * @since 0.1
	 */
	public function delete() {
		return $this->getDarProvider()->delete($this);
	}
	
	/**
	 * 取得属性值
	 *
	 * @param string $name 属性名
	 * @param array $query 如果是关系查找，则可以使用此查询条件
	 * @return mixed
	 * @since 0.0.3
	 */
	public function getDarProperty($name, array $query = null) {	
		$value = null;
		$field = null;
		try {
			$field = $this->getDarTable()->getField($name);
		} catch (IDarFieldNotFoundException $e) {
			return IReflection::getProperty($this, $name);
		}
		
		//如果存在
		try {
			$value = IReflection::getProperty($this, $name);
		} catch(IException $e) {
			if ($field != null) {//default value
				$value = $this->getDarTable()->getField($name)->getDefaultValue();
			}
		}
		
		//是否是关联表
		if ($field != null) {
			$relation = $field->getRelation();
			if ($relation != null) {
				$type = $relation->getType();
				$mainValue = $this->getDarProperty($relation->getMainField()->getName());
				
				$provider = $this->getDarModel()->getProvider();
				if (in_array($type, array(IDarRelation::ONE_TO_ONE, IDarRelation::MANY_TO_ONE))) {
					$assocDomainClass = $relation->getAssoc()->getDomainClass();
					$assocDomain = null;
					if (!empty($query)) {
						eval("\$assocDomain={$assocDomainClass}::manager('{$assocDomainClass}')->findByQuery(\$query);");
						return $assocDomain;
					}
					else {
						$query = array(
							"cond" => "[" . $relation->getAssoc()->getName() . "." . $relation->getAssocField()->getName() . "]=:assocId",
							"params" => array(
								"assocId" => $mainValue
							) 
						);
						eval("\$assocDomain={$assocDomainClass}::manager('{$assocDomainClass}')->findByQuery(\$query);");
						return $assocDomain;
					}
				}
				else if (in_array($type, array(IDarRelation::ONE_TO_MANY))) {
					if (!empty($query)) {
						$query = IDarQuery::create($query);
						$condition = $query->getCondition();
						$queryName = uniqid("_");
						if ($condition != "") {							
							$query->setCondition($condition . " AND [" . $relation->getAssoc()->getName() . "." . $relation->getAssocField()->getName() . "]=:{$queryName}");
						}
						else {
							$query->setCondition("[" . $relation->getAssoc()->getName() . "." . $relation->getAssocField()->getName() . "]=:{$queryName}");
						}
						$query->addParameter($queryName, $mainValue);
						return $provider->findAll($relation->getAssoc(), $query);
					}
					else {
						return $provider->findAllByFields($relation->getAssoc(), array(
							$relation->getAssoc()->getName() . "." . $relation->getAssocField()->getName()=>$mainValue
						));
					}
				}
			}
		}
		
		return $value;
	}
	
	/**
	 * 设置属性值
	 *
	 * @param string $name 属性名
	 * @param mixed $value 属性值
	 * @return boolean
	 * @since 0.0.3
	 */
	public function setDarProperty($name, $value) {
		$oldValue = null;
		
		$field = null;
		try {
			$field = $this->getDarTable()->getField($name);
		} catch (IDarFieldNotFoundException $e) {
			return IReflection::setProperty($this, $name, $value);
		}
		
		//filter save
		if ($field != null) {
			$field->filterValue($value);
		}
		
		//如果是域对象
		if ($value instanceof IDarDomain) {
			$relation = $field->getRelation();
			if ($relation != null) {
				$value = $value->getDarProperty($relation->getAssocField()->getName());
			}
		}

		//set domain property
		IReflection::setProperty($this, $name, $value);
		
		//add to modified properties
		$this->modifiedProperties[$name] = $value;
		
		return true;
	}
	
	/**
	 * 设置列的值
	 * 
	 * 原则上不允许在provider以外使用
	 *
	 * @param array $columnValues 设置列的值
	 * @since 0.0.3
	 */
	public function setDarColumnValues(array $columnValues = null) {
		if (!is_array($columnValues)) {
			return;
		}
		$this->originProperties = $columnValues;
		foreach ($columnValues as $column=>$value) {
			if ($this->getDarTable()->hasColumn($column)) {
				$name = $this->getDarTable()->getFieldByColumn($column)->getName();
				$this->originProperties[$name] = $value;
				$this->setDarProperty($name, $value);
			}
			else {//如果不是列名，则直接赋值 @since 0.2.1
				$this->setDarProperty($column, $value);
			}
		}
	}
	
	/**
	 * 设置字段值
	 *
	 * @param array $values
	 * @since 0.0.3
	 */
	public function setDarFieldValues(array $values = null) {
		if (!is_array($values)) {
			return;
		}
		foreach ($values as $field=>$value) {
			$this->setDarProperty($this->getDarTable()->getField($field)->getName(), $value);
		}
	}
	
	/**
	 * 调用方法
	 *
	 * @param string $method 方法名
	 * @param array $arguments 参数列表
	 * @return mixed
	 * @since 0.0.3
	 */
	public function __call($method, $arguments) {
		$match = null;
		if (!is_array($arguments)) {
			$arguments = array();
		}
		$argNum = count($arguments);
		if (preg_match("/^get(.+)$/i", $method, $match) && $argNum == 0) {
			return $this->getDarProperty($this->lcFirst($match[1]));
		}
		else if (preg_match("/^set(.+)$/i", $method, $match) && $argNum = 1) {
			if (!empty($arguments)) {
				return $this->setDarProperty($this->lcFirst($match[1]), $arguments[0]);
			}
		}
		else {
			$reflection = new IReflection(get_class($this));
			return $reflection->invoke($this, $method, $arguments);
		}
	}
	
	/**
	 * 取得修改后的属性
	 *
	 * @since 0.1
	 */
	public function getModifiedProperties() {
		return $this->modifiedProperties;
	}
	
	/**
	 * 取得属性值
	 *
	 * @param string $var 属性名
	 * @return mixed
	 * @since 0.0.3
	 */
	public final function __get($var) {
		return $this->getDarProperty($var);
	}
	
	/**
	 * 设置属性值
	 *
	 * @param string $name 属性名
	 * @param mixed $value 属性值
	 * @return boolean
	 * @since 0.1
	 */
	public final function __set($name, $value) {
		return $this->setDarProperty($name, $value);
	}
	
	/**
	 * 将字符串的首字母小写
	 *
	 * @since 0.1
	 */
	private function lcFirst($string) {
		if (is_string($string) && strlen($string) > 0) {
			$string{0} = strtolower($string{0});
		}
		return $string;
	}
	
	/**
	 * 取得修改后的字段
	 *
	 * @return array
	 * @return 0.1
	 */
	public function getDarModifiedFields() {
		$fields = array();
		foreach ($this->modifiedProperties as $name=>$value) {
			$value = $this->getDarProperty($name);
			if ((is_scalar($value) || is_null($value)) && array_key_exists($name, $this->originProperties)) {
				$originValue = $this->originProperties[$name];
				if ($value !== $originValue) {
					$fields[] = $this->getDarTable()->getField($name);
				}
			}
			else {
				$fields[] = $this->getDarTable()->getField($name);
			}
		}
		return $fields;
	}
	
	/**
	 * 是否为脏数据(被修改过)
	 *
	 * @return boolean
	 * @since 0.2
	 */
	public function isDirty() {
		$fields = $this->getDarModifiedFields();
		return !empty($fields);
	}
	
	/**
	 * 取得字段值
	 * 
	 * 如果该字段和其他域对象有关联关系，则取出来的也是另外的域对象，如：
	 * <pre>
	 * $log = TLog::manager()->find(37);
	 * $log->fetch("category")->dump();
	 * </pre>
	 * 结果是：
	 * <pre>
	 * Array
	 * (
 	 *    [id] => 1
	 *    [name] => PHP
	 * )
	 * </pre>
	 * 如果使用：
	 * <pre>
	 * $log->category
	 * $log->getCategory()
	 * </pre>
	 * 则可能只能取出来数字“1”，视乎域对象中是否有category这个属性和是否有getCategory()这个方法，只有全为否的时候才会和
	 * fetch效果一致
	 *
	 * @param string $name 字段名
	 * @param array $query 如果是关系查找，则可以使用此查询条件
	 * @return mixed
	 * @since 0.1
	 */
	public function fetch($name, array $query = null) {
		return $this->getDarProperty($name, $query);
	}
	
	/**
	 * 取得主键值
	 *
	 * @return mixed
	 * @since 0.2
	 */
	public function getIdValue() {
		return $this->fetch($this->getDarId()->getName());
	}
	
	/**
	 * 判断一个键是否存在
	 *
	 * 实现了 ArrayAccess 接口对应方法
	 * 
	 * @param mixed $key 键
	 * @return boolean
	 * @since 0.2
	 */	
	public function offsetExists($key) {
		return true;
	}	
	
	/**
	 * 取得一个键对应的值
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param mixed $key 键
	 * @return mixed 值
	 * @since 0.2
	 */
	public function offsetGet($key) {
		return $this->getDarProperty($key);
	}
	
	/**
	 * 设置一个键对应的值
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param mixed $key 键
	 * @param mixed $item 值
	 * @since 0.2
	 */
	public function offsetSet($key, $item) {
		$this->setDarProperty($key, $item);
	}
	
	/**
	 * 销毁一个键对应的元素
	 * 
	 * 实现了 ArrayAccess 接口对应方法
	 *
	 * @param mixed $key 键
	 * @since 0.2
	 */
	public function offsetUnset($key) {
		$this->setDarProperty($key, null);
	}
	
	/**
	 * 转换成数组
	 *
	 * @return array
	 * @since 0.2
	 */
	public function toArray() {
		return get_object_vars($this);
	}
	
/**
	 * 取得迭代器
	 * 
	 * 实现了 IIterable 接口对应方法
	 *
	 * @return IMapIterator
	 * @since 0.2
	 */	
	public function getIterator() {
		$array = $this->toArray();
		return new IMapIterator($array);
	}	
}

?>